D:\a\csshw\csshw\src\utils\config.rs
Line | Count | Source |
1 | | //! Client and Daemon configuration structs. |
2 | | |
3 | | use serde_derive::{Deserialize, Serialize}; |
4 | | use std::env; |
5 | | use windows::Win32::System::Console::{ |
6 | | BACKGROUND_INTENSITY, BACKGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_INTENSITY, |
7 | | FOREGROUND_RED, |
8 | | }; |
9 | | |
10 | | /// Placeholder for the `<username>@<host>` argument to the chosen SSH program. |
11 | | const DEFAULT_USERNAME_HOST_PLACEHOLDER: &str = "{{USERNAME_AT_HOST}}"; |
12 | | |
13 | | /// Representation of the project configuration. |
14 | | /// |
15 | | /// Includes subcommand specific configurations for `client` and `daemon` subcommands |
16 | | /// as well es the cluster tags. |
17 | | #[derive(Serialize, Deserialize, Default, PartialEq, Debug)] |
18 | | pub struct Config { |
19 | | /// List of cluster tags. |
20 | | /// |
21 | | /// Includes the name of the cluster tag and a list of hostnames. |
22 | | pub clusters: Vec<Cluster>, |
23 | | /// Configuration relevant for the `client` subcommand. |
24 | | pub client: ClientConfig, |
25 | | /// Configuration relevant for the `daemon` subcommand. |
26 | | pub daemon: DaemonConfig, |
27 | | } |
28 | | |
29 | | /// Representation of the project configuration |
30 | | /// where everything is optional. |
31 | | /// |
32 | | /// Used to handle cases where only some or none of the configurations are present. |
33 | | /// Enables backwards compatiblity with configuration files written by older versions. |
34 | | #[derive(Serialize, Deserialize, Default)] |
35 | | pub struct ConfigOpt { |
36 | | #[allow(missing_docs)] |
37 | | pub clusters: Option<Vec<Cluster>>, |
38 | | #[allow(missing_docs)] |
39 | | pub client: Option<ClientConfigOpt>, |
40 | | #[allow(missing_docs)] |
41 | | pub daemon: Option<DaemonConfigOpt>, |
42 | | } |
43 | | |
44 | | impl From<ConfigOpt> for Config { |
45 | | /// Unwraps the existing configuration values or applies the default. |
46 | 15 | fn from(val: ConfigOpt) -> Self { |
47 | 15 | return Config { |
48 | 15 | clusters: val.clusters.unwrap_or_default(), |
49 | 15 | client: val.client.unwrap_or_default().into(), |
50 | 15 | daemon: val.daemon.unwrap_or_default().into(), |
51 | 15 | }; |
52 | 15 | } |
53 | | } |
54 | | |
55 | | impl From<Config> for ConfigOpt { |
56 | | /// Wraps all configuration values as options. |
57 | 1 | fn from(val: Config) -> Self { |
58 | 1 | return ConfigOpt { |
59 | 1 | clusters: Some(val.clusters), |
60 | 1 | client: Some(val.client.into()), |
61 | 1 | daemon: Some(val.daemon.into()), |
62 | 1 | }; |
63 | 1 | } |
64 | | } |
65 | | |
66 | | /// Representation of a cluster tag. |
67 | | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] |
68 | | pub struct Cluster { |
69 | | /// Name of the cluster tag, used to identify it. |
70 | | pub name: String, |
71 | | /// List of hostnames the cluster tag is an alias for. |
72 | | pub hosts: Vec<String>, |
73 | | } |
74 | | |
75 | | /// Representation of the `client` subcommand configurations. |
76 | | #[derive(Serialize, Deserialize, PartialEq, Debug)] |
77 | | pub struct ClientConfig { |
78 | | /// Full path to the SSH config. |
79 | | /// |
80 | | /// # Example |
81 | | /// |
82 | | /// `'C:\Users\<username>\.ssh\config'` |
83 | | pub ssh_config_path: String, |
84 | | /// Name of the program used to establish the SSH connection. |
85 | | /// # Example |
86 | | /// |
87 | | /// `'ssh'` |
88 | | pub program: String, |
89 | | /// List of arguments provided to the program. |
90 | | /// |
91 | | /// Must include the `username_host_placeholder`. |
92 | | /// |
93 | | /// # Example |
94 | | /// |
95 | | /// `['-XY', '{{USERNAME_AT_HOST}}']` |
96 | | pub arguments: Vec<String>, |
97 | | /// Placeholder string used to inject `<user>@<host>` into the list of arguments. |
98 | | /// |
99 | | /// # Example |
100 | | /// |
101 | | /// `'{{USERNAME_AT_HOST}}'` |
102 | | pub username_host_placeholder: String, |
103 | | } |
104 | | |
105 | | impl Default for ClientConfig { |
106 | | /// Returns a sensible default `ClientConfig`. |
107 | | /// |
108 | | /// # Returns |
109 | | /// |
110 | | /// `ClientConfig` with the following values: |
111 | | /// * `ssh_config_path` - `%USERPROFILE%\.ssh\config` |
112 | | /// * `program` - `ssh` |
113 | | /// * `arguments` - `-XY {{USERNAME_AT_HOST}}` |
114 | | /// * `usernamt_host_placeholder` - `{{USERNAME_AT_HOST}}` |
115 | | /// |
116 | | /// Note: %USERPROFILE% actually is resolved by us, so the actual value |
117 | | /// is whatever the environment variable at runtime points to. |
118 | 60 | fn default() -> Self { |
119 | 60 | return ClientConfig { |
120 | 60 | ssh_config_path: format!("{}\\.ssh\\config", env::var("USERPROFILE").unwrap()), |
121 | 60 | program: "ssh".to_string(), |
122 | 60 | arguments: vec![ |
123 | 60 | "-XY".to_string(), |
124 | 60 | DEFAULT_USERNAME_HOST_PLACEHOLDER.to_string(), |
125 | 60 | ], |
126 | 60 | username_host_placeholder: DEFAULT_USERNAME_HOST_PLACEHOLDER.to_string(), |
127 | 60 | }; |
128 | 60 | } |
129 | | } |
130 | | |
131 | | /// Representation of the `client` subcommand configurations |
132 | | /// where everything is optional. |
133 | | #[derive(Serialize, Deserialize)] |
134 | | pub struct ClientConfigOpt { |
135 | | #[allow(missing_docs)] |
136 | | pub ssh_config_path: Option<String>, |
137 | | #[allow(missing_docs)] |
138 | | pub program: Option<String>, |
139 | | #[allow(missing_docs)] |
140 | | pub arguments: Option<Vec<String>>, |
141 | | #[allow(missing_docs)] |
142 | | pub username_host_placeholder: Option<String>, |
143 | | } |
144 | | |
145 | | impl Default for ClientConfigOpt { |
146 | 13 | fn default() -> Self { |
147 | 13 | return ClientConfig::default().into(); |
148 | 13 | } |
149 | | } |
150 | | |
151 | | impl From<ClientConfigOpt> for ClientConfig { |
152 | | /// Unwraps the existing configuration values or applies the default. |
153 | 18 | fn from(val: ClientConfigOpt) -> Self { |
154 | 18 | let default = ClientConfig::default(); |
155 | 18 | return ClientConfig { |
156 | 18 | ssh_config_path: val.ssh_config_path.unwrap_or(default.ssh_config_path), |
157 | 18 | program: val.program.unwrap_or(default.program), |
158 | 18 | arguments: val.arguments.unwrap_or(default.arguments), |
159 | 18 | username_host_placeholder: val |
160 | 18 | .username_host_placeholder |
161 | 18 | .unwrap_or(default.username_host_placeholder), |
162 | 18 | }; |
163 | 18 | } |
164 | | } |
165 | | |
166 | | impl From<ClientConfig> for ClientConfigOpt { |
167 | | /// Wraps all configuration values as options. |
168 | 14 | fn from(val: ClientConfig) -> Self { |
169 | 14 | return ClientConfigOpt { |
170 | 14 | ssh_config_path: Some(val.ssh_config_path), |
171 | 14 | program: Some(val.program), |
172 | 14 | arguments: Some(val.arguments), |
173 | 14 | username_host_placeholder: Some(val.username_host_placeholder), |
174 | 14 | }; |
175 | 14 | } |
176 | | } |
177 | | |
178 | | /// Representation of the `daemon` subcommand configurations. |
179 | | #[derive(Serialize, Deserialize, PartialEq, Debug)] |
180 | | pub struct DaemonConfig { |
181 | | /// Height in pixel of the daemon console window. |
182 | | /// |
183 | | /// Note: we are [DPI Unaware][1] which means the number of pixels |
184 | | /// represents the `logical` scale, not the physical. |
185 | | /// |
186 | | /// [1]: https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows#dpi-unaware |
187 | | pub height: i32, |
188 | | /// Controls how the client console windows make use of the available screen space. |
189 | | /// |
190 | | /// * `> 0.0` - Aims for vertical rectangle shape. |
191 | | /// The larger the value, the more exaggerated the "verticality". |
192 | | /// Eventually the windows will all be columns. |
193 | | /// * `= 0.0` - Aims for square shape. |
194 | | /// * `< 0.0` - Aims for horizontal rectangle shape. |
195 | | /// The smaller the value, the more exaggerated the "horizontality". |
196 | | /// Eventually the windows will all be rows. |
197 | | /// `-1.0` is the sweetspot for mostly preserving a 16:9 ratio. |
198 | | pub aspect_ratio_adjustement: f64, |
199 | | /// Controls back- and foreground colors of the daemon console window. |
200 | | /// |
201 | | /// All [standard windows color combinations][1] are available: |
202 | | /// |
203 | | /// FOREGROUND_BLUE: 1 \ |
204 | | /// FOREGROUND_GREEN: 2 \ |
205 | | /// FOREGROUND_RED: 4 \ |
206 | | /// FOREGROUND_INTENSITY: 8 \ |
207 | | /// BACKGROUND_BLUE: 16 \ |
208 | | /// BACKGROUND_GREEN: 32 \ |
209 | | /// BACKGROUND_RED: 64 \ |
210 | | /// BACKGROUND_INTENSITY: 128 \ |
211 | | /// |
212 | | /// # Example |
213 | | /// |
214 | | /// White font on red background: 8 + 4 + 2 + 1 + 128 + 64 = `207` |
215 | | /// |
216 | | /// [1]: https://learn.microsoft.com/en-us/windows/console/console-screen-buffers#character-attributes |
217 | | pub console_color: u16, |
218 | | } |
219 | | |
220 | | impl Default for DaemonConfig { |
221 | | /// Returns a sensible default `DaemonConfig`. |
222 | | /// |
223 | | /// # Returns |
224 | | /// |
225 | | /// `DaemonConfig` with the following values: |
226 | | /// * `height` - `200` |
227 | | /// * `aspect_ratio_adjustment` - `-1.0` |
228 | | /// * `console_color` - `207` |
229 | 59 | fn default() -> Self { |
230 | 59 | return DaemonConfig { |
231 | 59 | height: 200, |
232 | 59 | aspect_ratio_adjustement: -1f64, |
233 | 59 | console_color: (FOREGROUND_INTENSITY |
234 | 59 | | FOREGROUND_RED |
235 | 59 | | FOREGROUND_GREEN |
236 | 59 | | FOREGROUND_BLUE |
237 | 59 | | BACKGROUND_INTENSITY |
238 | 59 | | BACKGROUND_RED) |
239 | 59 | .0, |
240 | 59 | }; |
241 | 59 | } |
242 | | } |
243 | | |
244 | | /// Representation of the `daemon` subcommand configurations |
245 | | /// where everything is optional. |
246 | | #[derive(Serialize, Deserialize)] |
247 | | pub struct DaemonConfigOpt { |
248 | | #[allow(missing_docs)] |
249 | | pub height: Option<i32>, |
250 | | #[allow(missing_docs)] |
251 | | pub aspect_ratio_adjustement: Option<f64>, |
252 | | #[allow(missing_docs)] |
253 | | pub console_color: Option<u16>, |
254 | | } |
255 | | |
256 | | impl Default for DaemonConfigOpt { |
257 | 12 | fn default() -> Self { |
258 | 12 | return DaemonConfig::default().into(); |
259 | 12 | } |
260 | | } |
261 | | |
262 | | impl From<DaemonConfigOpt> for DaemonConfig { |
263 | | /// Unwraps the existing configuration values or applies the default. |
264 | 18 | fn from(val: DaemonConfigOpt) -> Self { |
265 | 18 | let default = DaemonConfig::default(); |
266 | 18 | return DaemonConfig { |
267 | 18 | height: val.height.unwrap_or(default.height), |
268 | 18 | aspect_ratio_adjustement: val |
269 | 18 | .aspect_ratio_adjustement |
270 | 18 | .unwrap_or(default.aspect_ratio_adjustement), |
271 | 18 | console_color: val.console_color.unwrap_or(default.console_color), |
272 | 18 | }; |
273 | 18 | } |
274 | | } |
275 | | |
276 | | impl From<DaemonConfig> for DaemonConfigOpt { |
277 | | /// Wraps all configuration values as options. |
278 | 13 | fn from(val: DaemonConfig) -> Self { |
279 | 13 | return DaemonConfigOpt { |
280 | 13 | height: Some(val.height), |
281 | 13 | aspect_ratio_adjustement: Some(val.aspect_ratio_adjustement), |
282 | 13 | console_color: Some(val.console_color), |
283 | 13 | }; |
284 | 13 | } |
285 | | } |
286 | | |
287 | | #[cfg(test)] |
288 | | #[path = "../tests/utils/test_config.rs"] |
289 | | mod test_config; |